/*
 * GDevelop Core
 * Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
 * reserved. This project is released under the MIT License.
 */
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include <algorithm>
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/String.h"

using namespace std;

namespace gd {

gd::BehaviorMetadata MetadataProvider::badBehaviorInfo;
gd::ObjectMetadata MetadataProvider::badObjectInfo;
gd::InstructionMetadata MetadataProvider::badInstructionMetadata;
gd::ExpressionMetadata MetadataProvider::badExpressionMetadata;
gd::ExpressionMetadata MetadataProvider::badStrExpressionMetadata;
gd::PlatformExtension MetadataProvider::badExtension;

ExtensionAndMetadata<BehaviorMetadata>
MetadataProvider::GetExtensionAndBehaviorMetadata(const gd::Platform& platform,
                                                  gd::String behaviorType) {
  for (auto& extension : platform.GetAllPlatformExtensions()) {
    auto behaviorTypes = extension->GetBehaviorsTypes();
    for (std::size_t j = 0; j < behaviorTypes.size(); ++j) {
      if (behaviorTypes[j] == behaviorType)
        return ExtensionAndMetadata<BehaviorMetadata>(
            *extension, extension->GetBehaviorMetadata(behaviorType));
    }
  }

  return ExtensionAndMetadata<BehaviorMetadata>(badExtension, badBehaviorInfo);
}

const BehaviorMetadata& MetadataProvider::GetBehaviorMetadata(
    const gd::Platform& platform, gd::String behaviorType) {
  return GetExtensionAndBehaviorMetadata(platform, behaviorType).GetMetadata();
}

ExtensionAndMetadata<ObjectMetadata>
MetadataProvider::GetExtensionAndObjectMetadata(const gd::Platform& platform,
                                                gd::String objectType) {
  for (auto& extension : platform.GetAllPlatformExtensions()) {
    auto objectsTypes = extension->GetExtensionObjectsTypes();
    for (std::size_t j = 0; j < objectsTypes.size(); ++j) {
      if (objectsTypes[j] == objectType)
        return ExtensionAndMetadata<ObjectMetadata>(
            *extension, extension->GetObjectMetadata(objectType));
    }
  }

  return ExtensionAndMetadata<ObjectMetadata>(badExtension, badObjectInfo);
}

const ObjectMetadata& MetadataProvider::GetObjectMetadata(
    const gd::Platform& platform, gd::String objectType) {
  return GetExtensionAndObjectMetadata(platform, objectType).GetMetadata();
}

ExtensionAndMetadata<InstructionMetadata>
MetadataProvider::GetExtensionAndActionMetadata(const gd::Platform& platform,
                                                gd::String actionType) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& allActions = extension->GetAllActions();
    if (allActions.find(actionType) != allActions.end())
      return ExtensionAndMetadata<InstructionMetadata>(
          *extension, allActions.find(actionType)->second);

    const auto& objects = extension->GetExtensionObjectsTypes();
    for (const gd::String& extObjectType : objects) {
      const auto& allObjectsActions =
          extension->GetAllActionsForObject(extObjectType);
      if (allObjectsActions.find(actionType) != allObjectsActions.end())
        return ExtensionAndMetadata<InstructionMetadata>(
            *extension, allObjectsActions.find(actionType)->second);
    }

    const auto& autos = extension->GetBehaviorsTypes();
    for (std::size_t j = 0; j < autos.size(); ++j) {
      const auto& allAutosActions =
          extension->GetAllActionsForBehavior(autos[j]);
      if (allAutosActions.find(actionType) != allAutosActions.end())
        return ExtensionAndMetadata<InstructionMetadata>(
            *extension, allAutosActions.find(actionType)->second);
    }
  }

  return ExtensionAndMetadata<InstructionMetadata>(badExtension,
                                                   badInstructionMetadata);
}

const gd::InstructionMetadata& MetadataProvider::GetActionMetadata(
    const gd::Platform& platform, gd::String actionType) {
  return GetExtensionAndActionMetadata(platform, actionType).GetMetadata();
}

ExtensionAndMetadata<InstructionMetadata>
MetadataProvider::GetExtensionAndConditionMetadata(const gd::Platform& platform,
                                                   gd::String conditionType) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& allConditions = extension->GetAllConditions();
    if (allConditions.find(conditionType) != allConditions.end())
      return ExtensionAndMetadata<InstructionMetadata>(
          *extension, allConditions.find(conditionType)->second);

    const auto& objects = extension->GetExtensionObjectsTypes();
    for (const gd::String& extObjectType : objects) {
      const auto& allObjetsConditions =
          extension->GetAllConditionsForObject(extObjectType);
      if (allObjetsConditions.find(conditionType) != allObjetsConditions.end())
        return ExtensionAndMetadata<InstructionMetadata>(
            *extension, allObjetsConditions.find(conditionType)->second);
    }

    const auto& autos = extension->GetBehaviorsTypes();
    for (std::size_t j = 0; j < autos.size(); ++j) {
      const auto& allAutosConditions =
          extension->GetAllConditionsForBehavior(autos[j]);
      if (allAutosConditions.find(conditionType) != allAutosConditions.end())
        return ExtensionAndMetadata<InstructionMetadata>(
            *extension, allAutosConditions.find(conditionType)->second);
    }
  }

  return ExtensionAndMetadata<InstructionMetadata>(badExtension,
                                                   badInstructionMetadata);
}

const gd::InstructionMetadata& MetadataProvider::GetConditionMetadata(
    const gd::Platform& platform, gd::String conditionType) {
  return GetExtensionAndConditionMetadata(platform, conditionType)
      .GetMetadata();
}

ExtensionAndMetadata<ExpressionMetadata>
MetadataProvider::GetExtensionAndObjectExpressionMetadata(
    const gd::Platform& platform, gd::String objectType, gd::String exprType) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& objects = extension->GetExtensionObjectsTypes();
    if (find(objects.begin(), objects.end(), objectType) != objects.end()) {
      const auto& allObjectExpressions =
          extension->GetAllExpressionsForObject(objectType);
      if (allObjectExpressions.find(exprType) != allObjectExpressions.end())
        return ExtensionAndMetadata<ExpressionMetadata>(
            *extension, allObjectExpressions.find(exprType)->second);
    }
  }

  // Then check base
  for (auto& extension : extensions) {
    const auto& allObjectExpressions =
        extension->GetAllExpressionsForObject("");
    if (allObjectExpressions.find(exprType) != allObjectExpressions.end())
      return ExtensionAndMetadata<ExpressionMetadata>(
          *extension, allObjectExpressions.find(exprType)->second);
  }

  return ExtensionAndMetadata<ExpressionMetadata>(badExtension,
                                                  badExpressionMetadata);
}

const gd::ExpressionMetadata& MetadataProvider::GetObjectExpressionMetadata(
    const gd::Platform& platform, gd::String objectType, gd::String exprType) {
  return GetExtensionAndObjectExpressionMetadata(platform, objectType, exprType)
      .GetMetadata();
}

ExtensionAndMetadata<ExpressionMetadata>
MetadataProvider::GetExtensionAndBehaviorExpressionMetadata(
    const gd::Platform& platform, gd::String autoType, gd::String exprType) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& autos = extension->GetBehaviorsTypes();
    if (find(autos.begin(), autos.end(), autoType) != autos.end()) {
      const auto& allAutoExpressions =
          extension->GetAllExpressionsForBehavior(autoType);
      if (allAutoExpressions.find(exprType) != allAutoExpressions.end())
        return ExtensionAndMetadata<ExpressionMetadata>(
            *extension, allAutoExpressions.find(exprType)->second);
    }
  }

  // Then check base
  for (auto& extension : extensions) {
    const auto& allAutoExpressions =
        extension->GetAllExpressionsForBehavior("");
    if (allAutoExpressions.find(exprType) != allAutoExpressions.end())
      return ExtensionAndMetadata<ExpressionMetadata>(
          *extension, allAutoExpressions.find(exprType)->second);
  }

  return ExtensionAndMetadata<ExpressionMetadata>(badExtension,
                                                  badExpressionMetadata);
}

const gd::ExpressionMetadata& MetadataProvider::GetBehaviorExpressionMetadata(
    const gd::Platform& platform, gd::String autoType, gd::String exprType) {
  return GetExtensionAndBehaviorExpressionMetadata(platform, autoType, exprType)
      .GetMetadata();
}

ExtensionAndMetadata<ExpressionMetadata>
MetadataProvider::GetExtensionAndExpressionMetadata(
    const gd::Platform& platform, gd::String exprType) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& allExpr = extension->GetAllExpressions();
    if (allExpr.find(exprType) != allExpr.end())
      return ExtensionAndMetadata<ExpressionMetadata>(
          *extension, allExpr.find(exprType)->second);
  }

  return ExtensionAndMetadata<ExpressionMetadata>(badExtension,
                                                  badExpressionMetadata);
}

const gd::ExpressionMetadata& MetadataProvider::GetExpressionMetadata(
    const gd::Platform& platform, gd::String exprType) {
  return GetExtensionAndExpressionMetadata(platform, exprType).GetMetadata();
}

ExtensionAndMetadata<ExpressionMetadata>
MetadataProvider::GetExtensionAndObjectStrExpressionMetadata(
    const gd::Platform& platform, gd::String objectType, gd::String exprType) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& objects = extension->GetExtensionObjectsTypes();
    if (find(objects.begin(), objects.end(), objectType) != objects.end()) {
      const auto& allObjectStrExpressions =
          extension->GetAllStrExpressionsForObject(objectType);
      if (allObjectStrExpressions.find(exprType) !=
          allObjectStrExpressions.end())
        return ExtensionAndMetadata<ExpressionMetadata>(
            *extension, allObjectStrExpressions.find(exprType)->second);
    }
  }

  // Then check in functions of "Base object".
  for (auto& extension : extensions) {
    const auto& allObjectStrExpressions =
        extension->GetAllStrExpressionsForObject("");
    if (allObjectStrExpressions.find(exprType) != allObjectStrExpressions.end())
      return ExtensionAndMetadata<ExpressionMetadata>(
          *extension, allObjectStrExpressions.find(exprType)->second);
  }

  return ExtensionAndMetadata<ExpressionMetadata>(badExtension,
                                                  badStrExpressionMetadata);
}

const gd::ExpressionMetadata& MetadataProvider::GetObjectStrExpressionMetadata(
    const gd::Platform& platform, gd::String objectType, gd::String exprType) {
  return GetExtensionAndObjectStrExpressionMetadata(
             platform, objectType, exprType)
      .GetMetadata();
}

ExtensionAndMetadata<ExpressionMetadata>
MetadataProvider::GetExtensionAndBehaviorStrExpressionMetadata(
    const gd::Platform& platform, gd::String autoType, gd::String exprType) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& autos = extension->GetBehaviorsTypes();
    if (find(autos.begin(), autos.end(), autoType) != autos.end()) {
      const auto& allBehaviorStrExpressions =
          extension->GetAllStrExpressionsForBehavior(autoType);
      if (allBehaviorStrExpressions.find(exprType) !=
          allBehaviorStrExpressions.end())
        return ExtensionAndMetadata<ExpressionMetadata>(
            *extension, allBehaviorStrExpressions.find(exprType)->second);
    }
  }

  // Then check in functions of "Base object".
  for (auto& extension : extensions) {
    const auto& allBehaviorStrExpressions =
        extension->GetAllStrExpressionsForBehavior("");
    if (allBehaviorStrExpressions.find(exprType) !=
        allBehaviorStrExpressions.end())
      return ExtensionAndMetadata<ExpressionMetadata>(
          *extension, allBehaviorStrExpressions.find(exprType)->second);
  }

  return ExtensionAndMetadata<ExpressionMetadata>(badExtension,
                                                  badStrExpressionMetadata);
}

const gd::ExpressionMetadata&
MetadataProvider::GetBehaviorStrExpressionMetadata(const gd::Platform& platform,
                                                   gd::String autoType,
                                                   gd::String exprType) {
  return GetExtensionAndBehaviorStrExpressionMetadata(
             platform, autoType, exprType)
      .GetMetadata();
}

ExtensionAndMetadata<ExpressionMetadata>
MetadataProvider::GetExtensionAndStrExpressionMetadata(
    const gd::Platform& platform, gd::String exprType) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& allExpr = extension->GetAllStrExpressions();
    if (allExpr.find(exprType) != allExpr.end())
      return ExtensionAndMetadata<ExpressionMetadata>(
          *extension, allExpr.find(exprType)->second);
  }

  return ExtensionAndMetadata<ExpressionMetadata>(badExtension,
                                                  badStrExpressionMetadata);
}

const gd::ExpressionMetadata& MetadataProvider::GetStrExpressionMetadata(
    const gd::Platform& platform, gd::String exprType) {
  return GetExtensionAndStrExpressionMetadata(platform, exprType).GetMetadata();
}

bool MetadataProvider::HasAction(const gd::Platform& platform,
                                 gd::String name) {
  auto& extensions = platform.GetAllPlatformExtensions();

  for (auto& extension : extensions) {
    const auto& actions = extension->GetAllActions();
    if (actions.find(name) != actions.end()) return true;
  }

  return false;
}

bool MetadataProvider::HasObjectAction(const gd::Platform& platform,
                                       gd::String objectType,
                                       gd::String name) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& actions = extension->GetAllActionsForObject(objectType);
    if (actions.find(name) != actions.end()) return true;
  }

  // Then check in functions of "Base object".
  for (auto& extension : extensions) {
    const auto& actions = extension->GetAllActionsForObject("");
    if (actions.find(name) != actions.end()) return true;
  }

  return false;
}

bool MetadataProvider::HasBehaviorAction(const gd::Platform& platform,
                                         gd::String behaviorType,
                                         gd::String name) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& actions = extension->GetAllActionsForBehavior(behaviorType);
    if (actions.find(name) != actions.end()) return true;
  }

  // Then check in functions of "Base object".
  for (auto& extension : extensions) {
    const auto& actions = extension->GetAllActionsForBehavior("");
    if (actions.find(name) != actions.end()) return true;
  }

  return false;
}

bool MetadataProvider::HasCondition(const gd::Platform& platform,
                                    gd::String name) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& conditions = extension->GetAllConditions();
    if (conditions.find(name) != conditions.end()) return true;
  }

  return false;
}

bool MetadataProvider::HasObjectCondition(const gd::Platform& platform,
                                          gd::String objectType,
                                          gd::String name) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& conditions = extension->GetAllConditionsForObject(objectType);
    if (conditions.find(name) != conditions.end()) return true;
  }

  // Then check in functions of "Base object".
  for (auto& extension : extensions) {
    const auto& conditions = extension->GetAllConditionsForObject("");
    if (conditions.find(name) != conditions.end()) return true;
  }

  return false;
}

bool MetadataProvider::HasBehaviorCondition(const gd::Platform& platform,
                                            gd::String behaviorType,
                                            gd::String name) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& conditions =
        extension->GetAllConditionsForBehavior(behaviorType);
    if (conditions.find(name) != conditions.end()) return true;
  }

  // Then check in functions of "Base object".
  for (auto& extension : extensions) {
    const auto& conditions = extension->GetAllConditionsForBehavior("");
    if (conditions.find(name) != conditions.end()) return true;
  }

  return false;
}

bool MetadataProvider::HasExpression(const gd::Platform& platform,
                                     gd::String name) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& expressions = extension->GetAllExpressions();
    if (expressions.find(name) != expressions.end()) return true;
  }

  return false;
}

bool MetadataProvider::HasObjectExpression(const gd::Platform& platform,
                                           gd::String objectType,
                                           gd::String name) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& expressions = extension->GetAllExpressionsForObject(objectType);
    if (expressions.find(name) != expressions.end()) return true;
  }

  // Then check in functions of "Base object".
  for (auto& extension : extensions) {
    const auto& expressions = extension->GetAllExpressionsForObject("");
    if (expressions.find(name) != expressions.end()) return true;
  }

  return false;
}

bool MetadataProvider::HasBehaviorExpression(const gd::Platform& platform,
                                             gd::String behaviorType,
                                             gd::String name) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& expressions =
        extension->GetAllExpressionsForBehavior(behaviorType);
    if (expressions.find(name) != expressions.end()) return true;
  }

  // Then check in functions of "Base object".
  for (auto& extension : extensions) {
    const auto& expressions = extension->GetAllExpressionsForBehavior("");
    if (expressions.find(name) != expressions.end()) return true;
  }

  return false;
}

bool MetadataProvider::HasStrExpression(const gd::Platform& platform,
                                        gd::String name) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& expressions = extension->GetAllStrExpressions();
    if (expressions.find(name) != expressions.end()) return true;
  }

  return false;
}

bool MetadataProvider::HasObjectStrExpression(const gd::Platform& platform,
                                              gd::String objectType,
                                              gd::String name) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& expressions =
        extension->GetAllStrExpressionsForObject(objectType);
    if (expressions.find(name) != expressions.end()) return true;
  }

  // Then check in functions of "Base object".
  for (auto& extension : extensions) {
    const auto& expressions = extension->GetAllStrExpressionsForObject("");
    if (expressions.find(name) != expressions.end()) return true;
  }

  return false;
}

bool MetadataProvider::HasBehaviorStrExpression(const gd::Platform& platform,
                                                gd::String behaviorType,
                                                gd::String name) {
  auto& extensions = platform.GetAllPlatformExtensions();
  for (auto& extension : extensions) {
    const auto& expressions =
        extension->GetAllStrExpressionsForBehavior(behaviorType);
    if (expressions.find(name) != expressions.end()) return true;
  }

  // Then check in functions of "Base object".
  for (auto& extension : extensions) {
    const auto& expressions = extension->GetAllStrExpressionsForBehavior("");
    if (expressions.find(name) != expressions.end()) return true;
  }

  return false;
}

MetadataProvider::~MetadataProvider() {}
MetadataProvider::MetadataProvider() {}

}  // namespace gd
